#include "Game.h"
#include "StringUtility.h"
#include "Error.h"
#include "Timer.h"
#include "Graphics.h"
#include "EventPoller.h"
#include "FileIO.h"
#include "Audio.h"
#include "MyGL.h"
#include <string>
#include <SDL.h>
#include "AIManager.h"
#include "CollisionManager.h"

namespace Engine
{	
	static bool l = TheGame::Instance();

	Game::Game():m_gameRunning(false), m_currentGameState(nullptr)
	{	
		Engine::TheAIManager::Instance();
		Engine::TheCollisionManager::Instance();
	}

	void Game::SetWindowTitle(const std::string& t)
	{
		m_windowTitle = t;
		SDL_WM_SetCaption(m_windowTitle.c_str(), NULL);
	}

	void Game::UpdateGameObjects()
	{
		for(MapGameObject::iterator it = map_gobj.begin();
			it != map_gobj.end();
			++it)
		{
			(*it).second->Update();
		}
	}

	void Game::DrawGameObjects()
	{
		glShadeModel(GL_SMOOTH);
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
		glEnable(GL_COLOR_MATERIAL);
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

		for(MapGameObject::iterator it = map_gobj.begin();
			it != map_gobj.end();
			++it)
		{
			(*it).second->Draw();
		}
	}

	void Game::RegisterGameObject(GameObject* gobj)
	{
		auto it = map_gobj.find(gobj ->GetGameObjectID());
		if(it != map_gobj.end()) return;

		map_gobj[gobj ->GetGameObjectID()] = gobj;
	}

	void Game::UnregisterGameObject(unsigned int id)
	{
		auto it = map_gobj.find(id);
		if(it == map_gobj.end()) return;

		map_gobj.erase(it);
	}

	bool Game::RegisterGameState(GameState* gs)
	{
		//check if it has already been added
		auto it = m_gameStates.find(gs ->GetHash());
		if(it != m_gameStates.end())
		{
			std::string message;
			//if it has then give warning
			if(&it ->second == &gs)
			{
				message = "GameState ";
				message += gs ->GetName();
				message += " being added twice";
				Warning(message);
				return false;
			}
			//if a diferent gs has been added with the same name throw an error
			else
			{
				message = "Attempting to add multiple GameStates with the same name ";
				message += gs ->GetName();
				Error(message);
				return false;
			}
		}
		m_gameStates[gs ->GetHash()] = gs;
		return true;
	}

	bool Game::Initialize(const std::string& fileName)
	{
		StringSPtr fileStr(new std::string);

		XMLDocSPtr doc = LoadXMLFile(fileName, fileStr);

		if(doc.get() == nullptr) 
			return false;

		if(!doc ->first_node("Graphics"))
		{
			Error("Unable to initialize Graphics with settings from config file");
			return false;
		}

		if(!TheGraphics::Instance() ->Initialize(doc ->first_node("Graphics")))
		{
			Error("Unable to initialize Graphics");
			return false;
		}
		
		if(!doc ->first_node("StartingState"))
		{
			Error("No starting game state set");
			return false;
		}

		if(!doc ->first_node("WindowTitle"))
		{
			Error("WindowTitle not found in config file");
			return false;
		}
		SetWindowTitle(doc ->first_node("WindowTitle") ->value());

		if(!TheTimer::Instance() ->Initialize(doc ->first_node("Timer")))
		{
			Error("Unable to initialize Timer");
			return false;
		}

		if(!TheAudio::Instance() ->Initialize())
		{
			Error("Unable to initialize Audio");
			return false;
		}

		if(!TheEventPoller::Instance() ->Initialize())
		{
			Error("Unable to initialize EventPoller");
			return false;
		}

		std::string gsStr = doc ->first_node("StartingState") ->value();
		auto it = m_gameStates.find(Hash(gsStr));
		if(it == m_gameStates.end())
		{
			std::string error("Starting game state ");
			error += gsStr;
			error += " not found in config file";
			Error(error);

			return false;
		}
		SetCurrentGameState(it ->second);

		return true;
	}

	void Game::SetCurrentGameState(GameState* gs)
	{
		//end game if nullptr is passed 
		if(!gs) 
		{
			m_gameRunning = false;
			return;
		}

		//check if the gs is already the current gs
		if(m_currentGameState && (m_currentGameState ->GetHash() == gs ->GetHash()))
		{
			std::string message = "Attempting to set the same GameState ";
			message += gs ->GetName();
			message += " as current GameState again";
			Warning(message);
			return;
		}

		//check if the game state has been registered
		auto it = m_gameStates.find(gs ->GetHash());
		if(it == m_gameStates.end())
		{
			std::string message = "Attempting to set a GameState that has not been registered: ";
			message += gs ->GetName();
			Error(message);
			return;
		}

		if(m_currentGameState) m_currentGameState ->Deactivate();
		m_currentGameState = gs;
		gs ->Activate();
	}

	void Game::SetCurrentGameState(const std::string& name)
	{
		//end game if empty string is passed
		if(!name.size()) 
		{
			m_gameRunning = false;
			return;
		}

		unsigned int hash = Hash(name);
		//check if the gs is already the current gs
		if(m_currentGameState && (m_currentGameState ->GetHash() == hash))
		{
			std::string message = "Attempting to set the same GameState ";
			message += name;
			message += " as current GameState again";
			Warning(message);
			return;
		}

		//check if the game state has been registered
		auto it = m_gameStates.find(hash);
		if(it == m_gameStates.end())
		{
			std::string message = "Attempting to set a GameState that has not been registered: ";
			message += name;
			Error(message);
			return;
		}

		if(m_currentGameState) m_currentGameState ->Deactivate();
		m_currentGameState = m_gameStates[hash];
		m_currentGameState ->Activate();
	}

	void Game::Start()
	{
		if(!m_currentGameState)
			Error("Attempting to start game without an initial GameState being set");
		
		m_gameRunning = true;

		Graphics* graphics = TheGraphics::Instance();
		Timer* timer = TheTimer::Instance();
		EventPoller* eventPoller = TheEventPoller::Instance();

		while(m_gameRunning)
		{
			timer ->Update();
			eventPoller ->Update();
			m_currentGameState ->Update();
			graphics ->ClearScreen();
			m_currentGameState ->Draw();
			graphics ->FlipBuffers();
		}
	}
}